/*
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 * Copyright (c) 2023-2024 Jeremy Long. All Rights Reserved.
 */
package io.github.jeremylong.openvulnerability.client.nvd;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.annotation.JsonValue;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({"source", "type", "cvssData", "exploitabilityScore", "impactScore"})
public class CvssV3 implements Serializable {

    /**
     * Serialization version UID.
     */
    private static final long serialVersionUID = 3239377501678853019L;

    public CvssV3() {
    }

    public CvssV3(String source, Type type, CvssV3Data cvssData, Double exploitabilityScore, Double impactScore) {
        this.source = source;
        this.type = type;
        this.cvssData = cvssData;
        this.exploitabilityScore = exploitabilityScore;
        this.impactScore = impactScore;
    }

    /**
     * (Required)
     */
    @JsonProperty("source")
    private String source;
    /**
     * (Required)
     */
    @JsonProperty("type")
    private Type type;
    /**
     * JSON Schema for Common Vulnerability Scoring System version 3.0
     * <p>
     * (Required)
     */
    @JsonProperty("cvssData")
    private CvssV3Data cvssData;
    /**
     * CVSS subscore.
     */
    @JsonProperty("exploitabilityScore")
    @JsonPropertyDescription("CVSS subscore.")
    private Double exploitabilityScore;
    /**
     * CVSS subscore.
     */
    @JsonProperty("impactScore")
    @JsonPropertyDescription("CVSS subscore.")
    private Double impactScore;

    /**
     * (Required)
     *
     * @return source
     */
    @JsonProperty("source")
    public String getSource() {
        return source;
    }

    /**
     * (Required)
     *
     * @return type
     */
    @JsonProperty("type")
    public Type getType() {
        return type;
    }

    /**
     * JSON Schema for Common Vulnerability Scoring System version 3.0
     * <p>
     * (Required)
     *
     * @return cvssData
     */
    @JsonProperty("cvssData")
    public CvssV3Data getCvssData() {
        return cvssData;
    }

    /**
     * CVSS subscore.
     *
     * @return exploitabilityScore
     */
    @JsonProperty("exploitabilityScore")
    public Double getExploitabilityScore() {
        return exploitabilityScore;
    }

    /**
     * CVSS subscore.
     *
     * @return impactScore
     */
    @JsonProperty("impactScore")
    public Double getImpactScore() {
        return impactScore;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("CVSS:");
        sb.append(cvssData.getVersion() == null ? "" : cvssData.getVersion()).append("/AV:")
                .append(cvssData.getAttackVector() == null ? "" : cvssData.getAttackVector().value().substring(0, 1))
                .append("/AC:")
                .append(cvssData.getAttackComplexity() == null ? ""
                        : cvssData.getAttackComplexity().value().substring(0, 1))
                .append("/PR:")
                .append(cvssData.getPrivilegesRequired() == null ? ""
                        : cvssData.getPrivilegesRequired().value().substring(0, 1))
                .append("/UI:")
                .append(cvssData.getUserInteraction() == null ? ""
                        : cvssData.getUserInteraction().value().substring(0, 1))
                .append("/S:").append(cvssData.getScope() == null ? "" : cvssData.getScope().value().substring(0, 1))
                .append("/C:")
                .append(cvssData.getConfidentialityImpact() == null ? ""
                        : cvssData.getConfidentialityImpact().value().substring(0, 1))
                .append("/I:")
                .append(cvssData.getIntegrityImpact() == null ? ""
                        : cvssData.getIntegrityImpact().value().substring(0, 1))
                .append("/A:").append(cvssData.getAvailabilityImpact() == null ? ""
                        : cvssData.getAvailabilityImpact().value().substring(0, 1));
        if (exploitabilityScore != null) {
            sb.append("/E:").append(exploitabilityScore);
        }
        if (cvssData.getRemediationLevel() != null
                && cvssData.getRemediationLevel() != CvssV3Data.RemediationLevelType.NOT_DEFINED) {
            sb.append("/RL:").append(cvssData.getRemediationLevel().value().charAt(0));
        }
        if (cvssData.getReportConfidence() != null
                && cvssData.getReportConfidence() != CvssV3Data.ConfidenceType.NOT_DEFINED) {
            sb.append("/RC:").append(cvssData.getReportConfidence().value().charAt(0));
        }
        if (cvssData.getConfidentialityRequirement() != null
                && cvssData.getConfidentialityRequirement() != CvssV3Data.CiaRequirementType.NOT_DEFINED) {
            sb.append("/CR:").append(cvssData.getConfidentialityRequirement().value().charAt(0));
        }
        if (cvssData.getIntegrityRequirement() != null
                && cvssData.getIntegrityRequirement() != CvssV3Data.CiaRequirementType.NOT_DEFINED) {
            sb.append("/IR:").append(cvssData.getIntegrityRequirement().value().charAt(0));
        }
        if (cvssData.getAvailabilityRequirement() != null
                && cvssData.getAvailabilityRequirement() != CvssV3Data.CiaRequirementType.NOT_DEFINED) {
            sb.append("/AR:").append(cvssData.getAvailabilityRequirement().value().charAt(0));
        }
        if (cvssData.getModifiedAttackVector() != null
                && cvssData.getModifiedAttackVector() != CvssV3Data.ModifiedAttackVectorType.NOT_DEFINED) {
            sb.append("/MAV:").append(cvssData.getModifiedAttackVector().value().charAt(0));
        }
        if (cvssData.getModifiedAttackComplexity() != null
                && cvssData.getModifiedAttackComplexity() != CvssV3Data.ModifiedAttackComplexityType.NOT_DEFINED) {
            sb.append("/MAC:").append(cvssData.getModifiedAttackComplexity().value().charAt(0));
        }
        if (cvssData.getModifiedPrivilegesRequired() != null
                && cvssData.getModifiedPrivilegesRequired() != CvssV3Data.ModifiedPrivilegesRequiredType.NOT_DEFINED) {
            sb.append("/MPR:").append(cvssData.getModifiedPrivilegesRequired().value().charAt(0));
        }
        if (cvssData.getModifiedUserInteraction() != null
                && cvssData.getModifiedUserInteraction() != CvssV3Data.ModifiedUserInteractionType.NOT_DEFINED) {
            sb.append("/MUI:").append(cvssData.getModifiedUserInteraction().value().charAt(0));
        }
        if (cvssData.getModifiedScope() != null
                && cvssData.getModifiedScope() != CvssV3Data.ModifiedScopeType.NOT_DEFINED) {
            sb.append("/MS:").append(cvssData.getModifiedScope().value().charAt(0));
        }
        if (cvssData.getModifiedConfidentialityImpact() != null
                && cvssData.getModifiedConfidentialityImpact() != CvssV3Data.ModifiedCiaType.NOT_DEFINED) {
            sb.append("/MC:").append(cvssData.getModifiedConfidentialityImpact().value().charAt(0));
        }
        if (cvssData.getModifiedIntegrityImpact() != null
                && cvssData.getModifiedIntegrityImpact() != CvssV3Data.ModifiedCiaType.NOT_DEFINED) {
            sb.append("/MI:").append(cvssData.getModifiedIntegrityImpact().value().charAt(0));
        }
        if (cvssData.getModifiedAvailabilityImpact() != null
                && cvssData.getModifiedAvailabilityImpact() != CvssV3Data.ModifiedCiaType.NOT_DEFINED) {
            sb.append("/MA:").append(cvssData.getModifiedAvailabilityImpact().value().charAt(0));
        }
        return sb.toString();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        CvssV3 cvssV3 = (CvssV3) o;
        return Objects.equals(source, cvssV3.source) && type == cvssV3.type && Objects.equals(cvssData, cvssV3.cvssData)
                && Objects.equals(exploitabilityScore, cvssV3.exploitabilityScore)
                && Objects.equals(impactScore, cvssV3.impactScore);
    }

    @Override
    public int hashCode() {
        return Objects.hash(source, type, cvssData, exploitabilityScore, impactScore);
    }

    public enum Type {

        PRIMARY("Primary"), SECONDARY("Secondary");

        private final static Map<String, Type> CONSTANTS = new HashMap<>();

        static {
            for (Type c : values()) {
                CONSTANTS.put(c.value, c);
            }
        }

        private final String value;

        Type(String value) {
            this.value = value;
        }

        @JsonCreator
        public static Type fromValue(String value) {
            Type constant = CONSTANTS.get(value);
            if (constant == null) {
                throw new IllegalArgumentException(value);
            } else {
                return constant;
            }
        }

        @Override
        public String toString() {
            return this.value;
        }

        @JsonValue
        public String value() {
            return this.value;
        }

    }

}
